package com.ldr.custom.spring.v2.context;

import com.ldr.custom.spring.annotation.GPAutowried;
import com.ldr.custom.spring.annotation.GPController;
import com.ldr.custom.spring.annotation.GPService;
import com.ldr.custom.spring.v2.beans.GPBeanDefinition;
import com.ldr.custom.spring.v2.beans.GPBeanPostProcessor;
import com.ldr.custom.spring.v2.beans.GPBeanWrapper;
import com.ldr.custom.spring.v2.context.support.GPBeanDefinitionReader;
import com.ldr.custom.spring.v2.core.GPBeanFactory;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * IOC容器
 * v2版本只是涉及IOC
 */
public class GPApplicationContext implements GPBeanFactory {


    private String[] configLocations;
    private GPBeanDefinitionReader reader;
    //保存配置信息
    private Map<String, GPBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();


    //用来保证注册式单例Bean
    private Map<String, Object> beanCacheMap = new HashMap<>();
    //用来存储所有的被代理过的对象
    private Map<String, GPBeanWrapper> beanWrapperMap = new ConcurrentHashMap<>();

    public GPApplicationContext(String... locations) {
        this.configLocations = locations;
        refresh();
    }

    public void refresh() {

        //定位
        this.reader = new GPBeanDefinitionReader(this.configLocations);
        //加载
        List<String> beanDefinitions = this.reader.loadBeanDefinitions();
        //注册
        doRegister(beanDefinitions);
        //依赖注入（自动注入，lazy-init=false），自动调用getBean方法
        doAutowried();


    }

    //开始执行依赖注入
    private void doAutowried() {
        for (Map.Entry<String, GPBeanDefinition> beanDefinitionEntry : beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            if (!beanDefinitionEntry.getValue().isLazyInit()) {
                //获取Bean实例
                getBean(beanName);
            }
        }


        for (Map.Entry<String, GPBeanWrapper> wrapperEntry : this.beanWrapperMap.entrySet()) {

            //类上有@Autowried注解，进行依赖注入方法
            populateBean(wrapperEntry.getKey(), wrapperEntry.getValue().getWrapperInstance());
        }
    }

    public void populateBean(String beanName, Object instance) {
        Class<?> clazz = instance.getClass();
        if (!clazz.isAnnotationPresent(GPController.class)
                && !clazz.isAnnotationPresent(GPService.class)) {
            return;
        }

        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(GPAutowried.class)) continue;

            GPAutowried autowried = field.getAnnotation(GPAutowried.class);
            String autowriedName = autowried.value().trim();
            if ("".equals(autowriedName)) {
                autowriedName = field.getType().getName();
            }

            field.setAccessible(true);

            try {
                field.set(instance, this.beanWrapperMap.get(autowriedName).getWrapperInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        }
    }

    //真正的将BeanDefinition注册到BeanDefinitionMap
    private void doRegister(List<String> beanDefinitions) {
        //beanName三种情况：
        //1 默认是类名首字母小写
        //2 自定义名称
        //3 接口注入

        try {
            for (String className : beanDefinitions) {

                Class<?> beanClass = Class.forName(className);

                //如果是一个接口，是不能实例化的
                //用它的实现类来实例化
                if (beanClass.isInterface()) continue;


                GPBeanDefinition GPBeanDefinition = reader.registerBean(className);
                if (GPBeanDefinition != null) {
                    beanDefinitionMap.put(GPBeanDefinition.getFactoryBeanName(), GPBeanDefinition);
                }


                Class<?>[] interfaces = beanClass.getInterfaces();
                for (Class<?> i : interfaces) {
                    //如果是多个实现类，只能覆盖
                    //spring没那么智能
                    //这个时候，可以自定义名字
                    this.beanDefinitionMap.put(i.getName(), GPBeanDefinition);
                }


                //到这里为止，容器初始化完毕
            }
        } catch (Exception e) {
            e.printStackTrace();
        }


    }


    /**
     * 通过读取BeanDefinition中的信息
     * 然后通过反射机制创建一个实例并返回
     * Spring做法是，不会把最原始对象放出去，会用一个BeanWrapper来进行一次包装
     * 装饰器模式：1、保留原来的OOP关系 2、需要对它进行扩展，增强（为以后AOP打基础）
     */
    @Override
    public Object getBean(String beanName) {

        GPBeanDefinition GPBeanDefinition = beanDefinitionMap.get(beanName);


        if (GPBeanDefinition == null) {
            return null;
        }

        try {

            //生成通知事件
            GPBeanPostProcessor GPBeanPostProcessor = new GPBeanPostProcessor();


            Object instance = instanceBean(GPBeanDefinition);

            if (instance == null) return null;
            //在实例初始化以前调用一次
            GPBeanPostProcessor.postProcessBeforeInitialization(instance, beanName);

            GPBeanWrapper GPBeanWrapper = new GPBeanWrapper(instance);
            GPBeanWrapper.setPostProcessor(GPBeanPostProcessor);

            this.beanWrapperMap.put(beanName, GPBeanWrapper);

            //在实例初始化以后调用一次
            GPBeanPostProcessor.postProcessAfterInitialization(instance, beanName);


            // populateBean(beanName,instance);

            //通过这样一调用，相当于给我们自己留有了可操作的空间
            return this.beanWrapperMap.get(beanName).getWrapperInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 传一个Beanfinition，就返回一个实例Bean
     *
     * @param GPBeanDefinition
     * @return
     */
    private Object instanceBean(GPBeanDefinition GPBeanDefinition) {

        Object instance = null;

        String beanClassName = GPBeanDefinition.getBeanClassName();

        try {
            synchronized (this) {

                if (this.beanCacheMap.containsKey(beanClassName)) {
                    instance = this.beanCacheMap.get(beanClassName);
                } else {
                    Class<?> clazz = Class.forName(beanClassName);
                    instance = clazz.newInstance();
                    this.beanCacheMap.put(beanClassName, instance);
                }
            }
            return instance;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

        return null;
    }


    public String[] getBeanDefinitionNames() {


        return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
    }

    public int getBeanDefinitionCount() {
        return this.beanDefinitionMap.size();
    }


    public Properties getConfig(){
        return this.reader.getConfig();
    }


}
